#!/usr/bin/ruby

#

# Copyright (c) 2006-2009 National ICT Australia (NICTA), Australia

#

# Permission is hereby granted, free of charge, to any person obtaining a copy

# of this software and associated documentation files (the "Software"), to deal

# in the Software without restriction, including without limitation the rights

# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

# copies of the Software, and to permit persons to whom the Software is

# furnished to do so, subject to the following conditions:

#

# The above copyright notice and this permission notice shall be included in

# all copies or substantial portions of the Software.

#

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE

# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

# THE SOFTWARE.

#

#

# = test_xmpp_peering.rb

#

# == Description

#

# This is a small test application which verifies that 2 XMPP PubSub servers 

# are correctly talking to each other via the server-2-server protocol for 

# the purpose of OMF federation.

#

#



require "rubygems"

require "optparse"

require "xmpp4r"

require "xmpp4r/pubsub"

require "xmpp4r/pubsub/helper/servicehelper.rb"

require "xmpp4r/pubsub/helper/nodebrowser.rb"

require "xmpp4r/pubsub/helper/nodehelper.rb"

include Jabber



#Jabber::debug=true



class MyServiceHelper < Jabber::PubSub::ServiceHelper

  #

  # Perform a 'unsubscribe_from' from scratch

  #

  def unsubscribe_from_fixed (node,subid)

    iq = basic_pubsub_query(:set)

    sub = REXML::Element.new('unsubscribe')

    sub.attributes['node'] = node

    sub.attributes['jid'] = @stream.jid.strip.to_s

    sub.attributes['subid']=subid

    iq.pubsub.add(sub)

    ret = false

    @stream.send_with_id(iq) do |reply|

      ret = reply.kind_of?(Jabber::Iq) and reply.type == :result

    end # @stream.send_with_id(iq)

    ret  

  end

end



###############################################################################

#

# The class that handles our interactions with the server 

#

###############################################################################



class PubSubTester

  

  attr_reader :userJID, :userPWD, :serverJID, :serviceJID, :queue



  def initialize(userJID, userPWD, serverJID, serviceJID, listen = true)

    @format = REXML::Formatters::Pretty.new

    @userJID = "#{userJID}@#{serverJID}"

    @userPWD = userPWD

    @serverJID = serverJID

    #@serviceJID = serviceJID

    @serviceJID = "pubsub.#{serviceJID}"

    # Processing Q for incoming events

    @queue = Queue.new

    Thread.new {

      while event = @queue.pop

        process_event(event)

      end

    }



    # Create and Connect a Client

    @clientHelper = Jabber::Client.new(@userJID)

    @clientHelper.connect(@serverJID)

    begin

      @clientHelper.register(@userPWD)

    rescue Exception => ex

      if ("#{ex}" != "conflict: ")

        then raise "Failed to register user #{@userJID} - Error: '#{ex}'"

      end

    end

    @clientHelper.auth(@userPWD)

    @clientHelper.send(Jabber::Presence.new)

    #puts "\nCONNECT OK - userJID: '#{@userJID}' - serverJID: '#{@serverJID}'\n"



    # Create Service Helper

    @service = MyServiceHelper.new(@clientHelper, @serviceJID)

    #puts "\nSERVICE OK - serviceJID: '#{@serviceJID}'\n"

    @browser = Jabber::PubSub::NodeBrowser.new(@clientHelper)

    #puts "\nBROWSER OK - serviceJID: '#{@serviceJID}'\n"



    # Start our Event Callback, which will process Events from

    # the nodes we will subscribe to

    @service.add_event_callback { |event|

      if listen 

        @queue << event

      end

    }

  end



  def process_event (event)

    begin

        incomingPubSubNode =  event.first_element("items").attributes['node']

        eventItem = event.first_element("items").first_element("item")

        eventBody = eventItem.first_element("message").first_element("body")

        puts "----"

        puts "LISTENER - Received a message on Topic: '#{incomingPubSubNode}'"

        puts "LISTENER - Message is: '#{eventBody.to_s}'"

        #puts "FULL MSG : '#{event.to_s}'"

        puts "----"

    rescue Exception => ex

      puts "----"

      puts "RAW XMPP EVENT"

      puts "#{event.to_s}"

      puts "----"

      return

    end

  end



  def send (node, message)

    item = Jabber::PubSub::Item.new

    payload = message

    msg = Jabber::Message.new(nil, payload)

    item.add(msg)

    begin

      @service.publish_item_to("#{node}", item)

    rescue Exception => ex

      puts "Failed sending to '#{node}'"

      puts "Error: '#{ex}'"

      puts "Msg: '#{payload}'"

      return

    end

    puts "Sent msg to '#{node}' - '#{payload}'"

  end



  def create(node)

    @service.create_node(node, Jabber::PubSub::NodeConfig.new(nil,{

        "pubsub#title" => "#{node}",

        #"pubsub#node_type" => "flat",

        #"pubsub#node_type" => "leaf",

        "pubsub#persist_items" => "1",

        "pubsub#max_items" => "1",

        "pubsub#notify_retract" => "0",

        "pubsub#publish_model" => "open"}))

  end 



  def close ; @clientHelper.close ; end

  def delete(node) ; @service.delete_node(node) ; end 

  def getconfig(node) ; @service.get_config_from(node) ; end 

  def setconfig(node, config) ; @service.set_config_for(node, config) ; end 

  def join(node) ; @service.subscribe_to(node) ; end 

  def leave(node, id) ; @service.unsubscribe_from_fixed(node, id) ; end 

  def listsub() ; return @service.get_subscriptions_from_all_nodes ; end

  def listall(server = @serviceJID) ; return @browser.nodes(@serviceJID) ; end



  def pp(inxml)

    out = String.new

    @format.write(inxml, out)

    puts out

  end



end



###############################################################################

#

# Below are the test definitions

#

###############################################################################



def test(tester, name, description, test_block)

  puts "------------------------"

  puts "#{name} - #{description}"

  puts "Remote Server: #{tester[:client].serviceJID}"

  res = :FAILED

  begin

    res = test_block.call(tester)

  rescue Exception => ex

    puts "#{name} - ERROR: (#{ex.class}) #{ex}"

    puts "#{name} - TRACE: \n#{ex.backtrace.join("\n\t")}"

  end

  puts "------------------------"

  puts "#{name} - RESULT: #{res}"

  puts "------------------------\n\n"

  if res == :FAILED

    puts "Test '#{name}' has failed, please find and fix the cause, then "+

         "re-run this program.\n\n"

    exit(-1)

  end

end



def test1(tester)

  test_block = lambda { |t|

    list = t[:client].listall()

    list[0,3].each { |node|

      puts "  Topic: '#{node}'"

    }

    puts "  etc..."

    return :PASSED

  }

  test(tester, 'test1', 'List topics of Remote Server', test_block)

end



def test2(tester)

  test_block = lambda { |t|

    topic = t[:client].create(t[:topic])

    list = t[:client].listall()

    puts "  Created Topic: '#{t[:topic]}'"

    return !list.index(t[:topic]).nil? ? :PASSED : :FAILED

  }

  test(tester, 'test2', 'Create a Topic on Remote Server', test_block)

end



def test3(tester)

  test_block = lambda { |t|

    puts "  Subscribing to Topic: '#{t[:topic]}'"

    topic = t[:client].join(t[:topic])

    puts "  Getting list of subscribed Topics: '#{t[:topic]}'"

    list = t[:client].listsub()

    list.each { |sub|

      puts "    - subscribed to: '#{sub.node}'  (subID: '#{sub.subid}')"

    }

    tlist = list.collect { |sub| sub.node }

    return !tlist.index(t[:topic]).nil? ? :PASSED : :FAILED

  }

  test(tester, 'test3', 'Subscribing to a Topic on Remote Server', test_block)

end



def test4(tester)

  test_block = lambda { |t|

    puts "  Publishing to Topic: '#{t[:topic]}'"

    puts "  Text to publish: '#{t[:text]}'"

    t[:client].send(t[:topic], t[:text])

    sleep 2

    return :PASSED

  }

  test(tester, 'test4', 'Publishing to a Topic on Remote Server', test_block)

end



def test5(tester)

  test_block = lambda { |t|

    puts "  Subscribing to Topic: '#{t[:topic]}'"

    list = t[:client].listsub()

    puts "  Looking for subscription... "

    list.each { |sub|

      if sub.node == t[:topic]

        begin 

          puts "    - unsubscribing: '#{sub.node}' (subid '#{sub.subid}')"

          t[:client].leave(sub.node, sub.subid)

        rescue Exception => ex

          # it is ok to have that specific exception with OpenFire here.

          raise ex unless "#{ex}" == "unexpected-request: "  

        end

      end

    }

    return :PASSED

  }

  test(tester, 'test5', 'Unsubscribe to a Topic on Remote Server', test_block)

end



def test6(tester)

  test_block = lambda { |t|

    puts "  Deleting the Topic: '#{t[:topic]}'"

    t[:client].delete(t[:topic])

    list = t[:client].listall()

    return list.index(t[:topic]).nil? ? :PASSED : :FAILED

  }

  test(tester, 'test6', 'Deleting a Topic on Remote Server', test_block)

end



###############################################################################

#

# Finally the main loop... 

#

###############################################################################



# Define Banner and Options

@username = @password = @server = @remote = @topic = nil

@opts = OptionParser.new

@opts.banner = "\nTest XMPP Peering\n\n"+

               "Usage: #{$0} [OPTIONS]\n\n"

@opts.on("-u", "--username NAME", 

         "Username to login to home server") {|n| @username = n }

@opts.on("-p", "--password PASS", 

         "Password to login to home server") {|n| @password = n }

@opts.on("-s", "--server HOST", 

         "Hostname for home server") {|n| @server = n }

@opts.on("-r", "--remote HOST", 

         "Hostname for remote server (default= home server") { |n| @remote = n }

@opts.on("-t", "--topic TOPIC", 

         "Topic name to use for test (optional)") {|n| @topic = n }



def show_help() puts @opts ; puts "\n" ; exit;  end

@opts.parse(ARGV)

show_help if !@username || !@password || !@server 



begin

  @remote = @server if @remote.nil?

  puts "\nConnecting to home server '#{@server}' as user: '#{@username}' with "+

       "pwd: '#{@password}'"

  puts "Remote server set to: '#{@remote}'"

  c = PubSubTester.new(@username, @password, @server, @remote, true) || nil

  raise "Could not connect!!!" if c.nil?



  test_time = Time.now

  tester = {:client => c,

            :topic => @topic.nil? ? "testing_#{test_time.to_i}" : @topic,

            :text => "testing text at #{test_time.to_s}"}



  puts "\nRunning Tests...\n"



  test1(tester)

  test2(tester)

  test3(tester)

  test4(tester)

  test5(tester)

  test6(tester)



rescue SystemExit => ex

rescue Exception => ex

  puts "ERROR: #{ex}"

  puts "TRACE: \n#{ex.backtrace.join("\n\t")}"

ensure

  c.close if !c.nil?

end



